package algo_lignes_sites.data.junction;

import algo_lignes_sites.data.Data_ls;
import algo_lignes_sites.data.force.Force;
import algo_lignes_sites.data.force.ForceAccumulator;
import algo_lignes_sites.data.force.ForceAccumulator.TypeForce;
import algo_lignes_sites.data.path.PathDER;
import algo_lignes_sites.data.path.PathREF;
import algo_lignes_sites.data.segment.SegmentDER;
import algo_lignes_sites.params.Params_ls;
import commons.data.I_Data.DataType;
import commons.data.Zone;
import commons.logs.Logger;
import commons.utils.UtilsCalcul;
import commons.utils.UtilsGeom;
import commons.utils.primitive_geom.Point_d;
import commons.utils.primitive_geom.Rect_d;
import gnu.trove.set.hash.THashSet;
import java.util.ArrayList;
import java.util.HashSet;


public class JunctionDER extends A_Junction {

	// Lien vers le point REF equivalent
	public JunctionREF jREF;
	public JunctionREF getJunctionREF() {return jREF;}
	
		
	// Liste des segments dont this est une des extrémités
	//  si plus de 3 seg, on stocke le surplus dans un vecteur créé uniquement dans ce cas, on economise ainsi de la mémoire
	// Servent notamment à récupérer les forces qu'ils ont stocké pour leurs extrémités, et les appliquer aux junctions
	// Ces segments sont alimentés lors de la fin des pre-traitement lorsque Data_ls appelera
	// path.computeVSegment() sur chaque Path : a ce moment, de nouveaux SegmentDER seront créés et leur junctino extrémités seront
	//  prévenues via addSegConnected()
	public int nbSeg;
	public SegmentDER seg1;
	public SegmentDER seg2;
	public ArrayList<SegmentDER> vSeg_autre;
	public void addSegConnected(SegmentDER seg) {
		if (nbSeg==0) {
			seg1=seg;
			nbSeg=1;
		} else if (nbSeg==1) {
			seg2=seg;
			nbSeg=2;
		} else { //nbSeg >= 2
			if (vSeg_autre==null) vSeg_autre = new ArrayList<SegmentDER>();
			vSeg_autre.add(seg);
			nbSeg++;
		}
	}

	public JunctionDER (JunctionREF jREF) {
		super(jREF.x, jREF.y);
		
		this.jREF = jREF;
		jREF.jDER = this;

		this.typeJunction = jREF.typeJunction;
		this.infoDebug = jREF.infoDebug;

		if (jREF.isNode()) {
			this.nodeData = new NodeData(jREF.nodeData.attributes, jREF.nodeData.zones, jREF.nodeData.contrainteGeom, jREF.nodeData.echelleGeneralization, jREF.nodeData.color, jREF.nodeData.vNodeInitFusionne);

			this.nodeData.contrainteGeom = jREF.nodeData.contrainteGeom;
			this.nodeData.echelleGeneralization = jREF.nodeData.echelleGeneralization;
			this.nodeData.color = jREF.nodeData.color;

			this.nodeData.rayon_Simplification_m = jREF.nodeData.rayon_Simplification_m; // ne sert plus en version DER -> uniquement pour l'affichage dans la couche de contrainte geom
			
			this.nodeData.complexite_apresSimplif = jREF.nodeData.complexite_apresSimplif;
			this.nodeData.factReductionInfluenceNode = jREF.nodeData.factReductionInfluenceNode;
		}


		// Reproduction de la topologie de la junction REF (lien avec les path)
		// Les liens vers les path DER connectés sont possibles, car à la création
		//  d'une JunctionDER comme ici, le Data s'est chargé de créer au préalable
		//  tous les PathDER (vides initialement) associés aux PathREF
		int nbPathContenantREF = jREF.getNbPathContenant();
		for (int i=0; i<nbPathContenantREF; i++) {
			PathREF pathREF = (PathREF) jREF.getPathContenantNum(i);
			assert(pathREF.getPathDER() != null) : "PathDER pas encore créé lors de la reproduction des liens topologiques : " + pathREF;
			//if (pathREF.getPathDER() != null) // TODO_A_VIRER
			this.addPathContenant(pathREF.getPathDER());
		}
		
		// init des segments connectés à this
		nbSeg = 0;
		seg1 = null;
		seg2 = null;
		vSeg_autre = null;

		initForces();

		// Node influenceur
		nbInfluenceur_NODE = 0;
		tab_Influenceur_NODE = null;
		tab_factorInfluencement_NODE = null;

		// PtMultiple influenceur
		nbInfluenceur_PTMULTIPLE = 0;
		tab_Influenceur_PTMULTIPLE = null;
		tab_factorInfluencement_PTMULTIPLE = null;

	    dxVitesse = 0;
	    dyVitesse = 0;
	}


	@Override
	protected double distTo_ref(A_Junction j) {
		return jREF.distTo(((JunctionDER)j).jREF);
	}


	//**************************************************************************
	//**************************************************************************
	//       FORCES
	//**************************************************************************
	//**************************************************************************

/* VERSION <V20
	public void computeForcesRepuls(Data_ls data) {
		// Vecteurs des Path et des points proches du point en cours
		//  (qui ont une chance d'avoir une interaction avec
		double rayonInteraction;
		Rect_d rectInteraction;

		
		double factAttenuationRepulsion = getFactAttenuationRepulsion_complet();
		double factAttenuationRepulsion_horsZoneDense = getFactAttenuationRepulsion_horsZoneDense();
		
		//***** Calcul repulsion ligne / ligne		
		rayonInteraction = this.diametre_reel_m/2 + data.maxEpaisseurPath_reel_m/2 +
				           this.epaisseurDissymetrie_reel_m + data.maxEpaisseurDissymetrie_reel_m +
						   data.maxEspaceMiniAutourPath_reel_m;

		// TODO_KO PERF : pas de recréation de rect a chaque fois : mise en variable d'instance puis changement de coord -> c'est plus long !!
		rectInteraction = new Rect_d(this, rayonInteraction*2, rayonInteraction*2);
		//rectInteraction.setCoordFrom(this, rayonInteraction*2, rayonInteraction*2);

		ArrayList<PathDER> vPathProche = data.getVPathDER(rectInteraction);
		if (vPathProche.size()>0)
			computeRepulsToLigne_v1 (vPathProche, factAttenuationRepulsion);

		
		//***** Calcul repulsion ligne / node
		rayonInteraction = data.maxDiametrePoste_reel_m + data.maxEspaceMiniAutourPoste_reel_m;
		rectInteraction = new Rect_d(this, rayonInteraction*2, rayonInteraction*2);
		ArrayList<JunctionDER> vNodeProche = data.getVNodeDER(rectInteraction);
		if (vNodeProche.size()>0)
			computeRepulsToNode (vNodeProche, factAttenuationRepulsion, factAttenuationRepulsion_horsZoneDense);

	}*/

	// Uniquement la repulsion des junctions type NODE
	public void computeForcesRepuls(Data_ls data) {
		assert(this.isNode());
		
		// Vecteurs des Path et des points proches du point en cours
		//  (qui ont une chance d'avoir une interaction avec
		double rayonInteraction;
		Rect_d rectInteraction;


		//***** Calcul repulsion seg -> node
		rayonInteraction = this.diametre_reel_m/2 + data.maxEpaisseurPath_reel_m/2 +
				           this.epaisseurDissymetrie_reel_m + data.maxEpaisseurDissymetrie_reel_m +
						   data.maxEspaceMiniAutourPath_reel_m;
		rectInteraction = new Rect_d(this, rayonInteraction*2, rayonInteraction*2);

		ArrayList<SegmentDER> vSegProche = data.getVSegmentDER(rectInteraction);
		if (vSegProche.size()>0)
			computeRepuls_Seg_Node (vSegProche);

		
		//***** Calcul repulsion node / node
		rayonInteraction = data.maxDiametreNode_reel_m + data.maxEspaceMiniAutourNode_reel_m;
		rectInteraction = new Rect_d(this, rayonInteraction*2, rayonInteraction*2);
		ArrayList<JunctionDER> vNodeProche = data.getVNodeDER(rectInteraction);
		if (vNodeProche.size()>0)
			computeRepuls_Node_Node (vNodeProche);//, factAttenuationRepulsion, factAttenuationRepulsion_horsZoneDense);

	}

	
	//**************************************************************************
	//  GESTION DE LA COMPLEXITE ET DE L'ATTENUATION DE MOUVEMENT
	//**************************************************************************
	// Notion 1)
	// Attenuation de repulsion dans les abord de nodes
	//  dépend de la proximité au node (du facteur d'entrainement en fait)
	//  et de la complexité du node : si node peu complexe (sous un seuil), alors on attenue moins la repulsion
	// Il y a egalement un mécanisme plus simple aux abord de points multiples
	//   -> l'atténuation ne dépend que du facteur d'entrainement au point multiple
	// Notion 2)
	// Atténuation de répulsion en zones dense, d'une manière générale (proche d'un node ou non)
	// dépend de la complexité de la zone, valeur calculée dans la junctionREF durant les pretraitements
	// On ne multiplie pas ces 2 facteur si une junction est concernée par les 2 notions

/*
    // Max dans le sens de la plus forte attenuation => facteur le plus proche de 0
	// calculé qu'une seule fois au debut de l'algo, alors que l'entrainement, lui, depend du mouvement du poste
	private double maxFactAttenuationRepulsion_ = -1;
	private double getMaxFactAttenuationRepulsion() {
		if (maxFactAttenuationRepulsion_ == -1)
			maxFactAttenuationRepulsion_ = Math.min(getMaxFactAttenuationRepulsion_node_ptMult(), getAttenuation_zoneDense());
		return maxFactAttenuationRepulsion_;
	}

	//**************************************************************************
	// NOTION 1 : Abord nodes


	// Max dans le sens de la plus forte attenuation => facteur le plus proche de 0
	private double maxFactAttenuationRepulsion_node_ptMult = -1;
	private double getMaxFactAttenuationRepulsion_node_ptMult() {
		if (maxFactAttenuationRepulsion_node_ptMult == -1)
			computeMaxAttenuationRepulsion_node_ptMult();
		return maxFactAttenuationRepulsion_node_ptMult;
	}
	private void computeMaxAttenuationRepulsion_node_ptMult() {
		// V18 : Simplification Adaptative des Nodes
		//  si la complexite du node est sous un seuil, on attenue moins la repulsion

		maxFactAttenuationRepulsion_node_ptMult = 1;
		
		// facteur d'entrainement des nodes
		for (int i=0 ; i<nbEntraineur_NODE ; i++) {
			JunctionDER node = tab_Entraineur_NODE[i];
			double factEntrainement = tab_factorForceEntrainement_NODE[i];
			maxFactAttenuationRepulsion_node_ptMult = Math.min(maxFactAttenuationRepulsion_node_ptMult, getAttenuationRepulsionForNode(node, factEntrainement));
		}
		
		// facteur entrainement points multiples
		for (int i=1 ; i<nbEntraineur_PTMULTIPLE ; i++) {
			double factEntrainement = tab_factorForceEntrainement_PTMULTIPLE[i];
			maxFactAttenuationRepulsion_node_ptMult = Math.min(maxFactAttenuationRepulsion_node_ptMult, getAttenuationRepulsionForPtMult(factEntrainement));
		}
	}
*/

	//**************************************************************************
	// Attenuation de repulsion
	// calculé qu'une seule fois au debut de l'algo (alors que l'entrainement, lui, depend du mouvement du poste)

	private double maxInfluence_node_ptMult = -1;
	private double minFactAttenuationRepulsion_node_ptMult = -1;
	private double meanFactAttenuationRepulsion_node_ptMult = -1;
	public double getMaxInfluence_node_ptMult() {
		//assert(maxInfluence_node_ptMult != -1); // ce test ne fonctionne pas en algo >=v22 dans le cas de node fusionné, recréés en post-traitement
		return maxInfluence_node_ptMult;
	}
	public double getMeanFactAttenuationRepulsion_node_ptMult() {
		//assert(meanFactAttenuationRepulsion_node_ptMult != -1); // ce test ne fonctionne pas en algo >=v22 dans le cas de node fusionné, recréés en post-traitement
		return meanFactAttenuationRepulsion_node_ptMult;
	}
	public double getMinFactAttenuationRepulsion_node_ptMult() {
		//assert(minFactAttenuationRepulsion_node_ptMult != -1); // ce test ne fonctionne pas en algo >=v22 dans le cas de node fusionné, recréés en post-traitement
		return minFactAttenuationRepulsion_node_ptMult;
	}
	
	private double factAttenuationRepulsion_complet = -1;
	private double factAttenuationRepulsion_horsZoneDense = -1;
	
	public double getFactAttenuationRepulsion_complet() {
		//assert(factAttenuationRepulsion_complet>=0); // ce test ne fonctionne pas en algo >=v22 dans le cas de node fusionné, recréés en post-traitement
		return factAttenuationRepulsion_complet;
	}
	public double getFactAttenuationRepulsion_horsZoneDense() {
		//assert(factAttenuationRepulsion_horsZoneDense>=0); // ce test ne fonctionne pas en algo >=v22 dans le cas de node fusionné, recréés en post-traitement
		return factAttenuationRepulsion_horsZoneDense;
	}


	private void computeMinMeanAttenuationRepulsion_node_ptMult() {
		// V18 : Simplification Adaptative des Nodes
		//  si la complexite du node est sous un seuil, on attenue moins la repulsion

		maxInfluence_node_ptMult = 0;
		minFactAttenuationRepulsion_node_ptMult = 1.0;
		meanFactAttenuationRepulsion_node_ptMult = 0;
		int denom = 0;
		
		// Influence des nodes
		for (int i=0 ; i<nbInfluenceur_NODE ; i++) {
			JunctionDER node = tab_Influenceur_NODE[i];
			double factInfluencement = tab_factorInfluencement_NODE[i];
			maxInfluence_node_ptMult = UtilsCalcul.max(maxInfluence_node_ptMult, factInfluencement);
			double attenuation = getAttenuationRepulsionForNode(node, factInfluencement);
			minFactAttenuationRepulsion_node_ptMult = UtilsCalcul.min(minFactAttenuationRepulsion_node_ptMult, attenuation);
			meanFactAttenuationRepulsion_node_ptMult += attenuation;
			denom++;
		}
		
		// Influence des points multiples
		for (int i=0 ; i<nbInfluenceur_PTMULTIPLE ; i++) {
			double factInfluencement = tab_factorInfluencement_PTMULTIPLE[i];
			maxInfluence_node_ptMult = UtilsCalcul.max(maxInfluence_node_ptMult, factInfluencement);
			double attenuation = getAttenuationRepulsionForPtMult(factInfluencement);
			minFactAttenuationRepulsion_node_ptMult = UtilsCalcul.min(minFactAttenuationRepulsion_node_ptMult, attenuation);
			meanFactAttenuationRepulsion_node_ptMult += attenuation;
			denom++;
		}
		if (denom>0)
			meanFactAttenuationRepulsion_node_ptMult /= denom;
		else
			meanFactAttenuationRepulsion_node_ptMult = 1;
	}

	
	// En V18, ce calcul est specifique, notamment si le node est suffisamment simple
	static public double getAttenuationRepulsionForNode(JunctionDER node, double influencementDeCeNode) {
		return 1-influencementDeCeNode*node.nodeData.factReductionInfluenceNode;
	}
	// pour les pt multiple, le calcul reste le calcul simple
	static public double getAttenuationRepulsionForPtMult(double influencementDeCePtMult) {
		return 1-influencementDeCePtMult;
	}
	
	public void computeFactAttenuationRepulsion() {
		computeMinMeanAttenuationRepulsion_node_ptMult();
		if (factAttenuationRepulsion_complet == -1 || factAttenuationRepulsion_horsZoneDense == -1) {
			// L'atténuation de repulsion vient de 2 facteurs :
			
			// Algo v23 11/03/2015 : on prend le min et non plus le mean cf. explication fichier commentaires 2015
			double facteur_Node_ptMult = getMinFactAttenuationRepulsion_node_ptMult(); // facteur lié à l'influence des nodes/pt mult
			
			double facteur_ZoneDense = getAttenuation_zoneDense(); // facteur liée à la densité de la zone

			// on prend par défaut l'atténuation la plus forte des 2
			factAttenuationRepulsion_complet = UtilsCalcul.min(facteur_Node_ptMult, facteur_ZoneDense);

			// pour la repulsion ligne / node, on ne prend pas la densité de la zone sinon, des nodes qui veulent bouger risquent de traverser
			//  des lignes qui seraient bloquée par une atténuation trop forte en zone dense
			// (car, pour rappel, les nodes ne sont pas affectés par la densité de la zone)
			factAttenuationRepulsion_horsZoneDense = facteur_Node_ptMult;
		}
	}

	
	
	
	// Force d'entrainement adaptative : change durant le traitement, car dépend du déplacement des nodes influenceurs !!!!
	// On va réduire la force d'entrainement pour les nodes simples, afin que les lignes alentours
	//  puissent se libérer plus facilement
	// Cependant, si le node a trop bougé, on remet progressivement la valeur par defaut
	//  car la force d'entrainement est censé donner une fluidité du tracé des lignes en cas de déplacement du node ...
	private double getFacteurEntrainementByNode(JunctionDER node, double influencementDeCeNode) {
		return influencementDeCeNode * getReductionEntrainement(node);
	}
	private double getReductionEntrainement(JunctionDER node) {
		// La réduction est la meme que celle pour l'atténuation de répulsion, par défaut
		double reductionEntrainement = node.nodeData.factReductionInfluenceNode;

		// Puis on revient vers 1 si le node a trop bougé (au dela de 1/3 de son diamètre -> annulation de la reduction)
		// -> on annule tres rapidement la reduction d'entrainement (cf. CRUA5 qui deconne sinon)
		// si seulement 1/2, alors les junctions antre CRUA5 et COULA peuvent s'entrechoquer et s'intersecter
		double deplacementNode = node.distTo(node.jREF);
		if (deplacementNode > node.diametre_reel_m/3) // TODO INI
			reductionEntrainement = 1;
		else {
			reductionEntrainement += deplacementNode/(node.diametre_reel_m/3);
			if (reductionEntrainement>1)
				reductionEntrainement = 1;
		}

		return reductionEntrainement;
	}


	//**************************************************************************
	// ZONES DENSES
	private double getAttenuation_zoneDense() {
		return jREF.attenuationMouvement_zoneDense;
	}



	//**************************************************************************
	//  CALCUL DES FORCES
	//**************************************************************************

//	static private boolean B_USE_COS = true;
//	static private double MIN_COS = 0.3;

	// TODO_BOF ALGO : et les dissymetries !!! -> mouai, la ligne sera écartée via Node->Seg
	//   du coup la ligne sera plus déplacée que le node, mais l'écartement final sera bon
	private void computeRepuls_Seg_Node(ArrayList<SegmentDER> vSegProche) {
		assert(this.isNode());
		// Pour chaque segment proche
		int sizeVSeg = vSegProche.size();
		for (int i=0; i<sizeVSeg; i++) {
	        SegmentDER seg = vSegProche.get(i);
			
			if (!seg.path.getEchelleGeneralization().bToBeGeneralized)
				continue;
			
			// Un node ne se fait pas repousser par les path qui lui sont connectés directement ou indirectement
			if (this.isNode() && seg.path.isConnectedToNode(this))
				continue;
			
			Point_d ptPlusProche = UtilsGeom.getClosestPt(seg, this);
			
			double ecartReel = this.distTo(ptPlusProche);
			double ecartMin = this.diametre_reel_m/2 + seg.path.epaisseurTrait_reel_m/2 + UtilsCalcul.max(this.espaceAutour_reel_m, seg.path.espaceAutour_reel_m);
			
			if (ecartReel < ecartMin) {
				// this est trop proche du segment : on met une force de répulsion
				double distPenetr = ecartMin - ecartReel;
				double xUnitDeplacement = (this.x - ptPlusProche.x) / ecartReel;
				double yUnitDeplacement = (this.y - ptPlusProche.y) / ecartReel;
				
				double xForce, yForce;
				xForce = xUnitDeplacement * distPenetr;// * factAttenuationRepulsion * attenuationSiDissymetrie;
				yForce = yUnitDeplacement * distPenetr;// * factAttenuationRepulsion * attenuationSiDissymetrie;

				nodeData.accuForceRepuls_Seg_Node.add(xForce, yForce);
			}
		}
	}
	
	private void computeRepuls_Node_Node(ArrayList<JunctionDER> vNodeProche) {
		assert(this.isNode());
		// Pour chaque node proche
		int sizeVNode = vNodeProche.size();
		for (int i=0; i<sizeVNode; i++) {
	        JunctionDER node = vNodeProche.get(i);
			
			// Pas de répulsion par soi-meme ...
			if (this == node)
				continue;
			
			if (!node.nodeData.echelleGeneralization.bToBeGeneralized)
				continue;
			
			double ecartReel = this.distTo(node);
			double ecartMin = this.diametre_reel_m/2 + node.diametre_reel_m/2 + UtilsCalcul.max(this.espaceAutour_reel_m, node.espaceAutour_reel_m);
			
			if (ecartReel < ecartMin) {
				// this est trop proche de node : on met une force de répulsion
				double distPenetr = ecartMin - ecartReel;
				double xUnitDeplacement = (this.x - node.x) / ecartReel;
				double yUnitDeplacement = (this.y - node.y) / ecartReel;
				
				double xForce, yForce;
				xForce = xUnitDeplacement * distPenetr;
				yForce = yUnitDeplacement * distPenetr;

				nodeData.accuForceRepuls_Node_Node.add(xForce, yForce);
			}
		}
	}

	/* OLD : V19
	private void computeRepulsToLigne_v1(ArrayList<PathDER> vPathProches, double factAttenuationRepulsion) {

		int sizeVPathProche = vPathProches.size();
		for (int i=0; i<sizeVPathProche; i++) {
	        PathDER path = vPathProches.get(i);
			if (!path.getEchelleGeneralization().bToBeGeneralized)
				continue;

			// pas de répulsion avec un path auquel le pt est connecté directement
			//if (this.faitPartieDuPath(path)) continue;
			// pas de répulsion avec un path de this, ou bien
			//  ayant le même superpath que l'un des path de this
			if (this.faitPartieDuMemePathOuSuperPath(path)) continue;

			// un node ne se fait pas repousser par les path qui lui sont connectés directement ou indirectement
			if (this.isNode() && path.isConnectedToNode(this)) continue;

			// ni par ceux dont l'une des extrémités est entrainée (dans l'influence) par le node
			// (c'est le cas limite où l'extrémité est entrainée, du fait que le path avant est dans l'influence du node
			//  mais que le path suivant est hors d'influence, mais a qd meme cette extrémité entrainée)
			// -> si on ne fait pas ce test, on peut avoir des phénomènes de "dérive"
			if (this.isNode() && (path.from_der().getFactorInfluencement_NODE(this)>0 || path.to_der().getFactorInfluencement_NODE(this)>0)) continue;


			//******* Calcul du point le plus proche entre this et le path *****
			Point_d ptPlusProche=null;
			Vector_d vectorDirection_path=null; // en le point le plus proche situé sur 'path'

			// Variable permettant d'accélérer la recherche du point le plus proche
			// Le diametre de la junction est soit son diametre propre pour les nodes
			//  soit l'épaisseur de son path le plus gros pour les autres types de junctions
			double distMaxSearch = this.diametre_reel_m/2 + path.epaisseurTrait_reel_m/2 +
					               this.epaisseurDissymetrie_reel_m + path.epaisseurDissymetrie_reel_m +
								   Math.max(this.espaceAutour_reel_m, path.espaceAutour_reel_m);

			vectorDirection_path = path.getClosestPointWithDirection(this, distMaxSearch);
			if (vectorDirection_path != null)
				ptPlusProche = vectorDirection_path.pt1;

			if (ptPlusProche == null)
				// le path se trouve etre à une distance > distMaxSearch de this
				// cf. l'index spatial donne les path à proximité mais des fois, ils sont plus loin
				continue;


			//******* Calcul de la dist min a respecter entre this et path *****
			double ecartMin = 0;
			
			// Cas spécial du node : on ne parcourt pas tous ses path (qui sont censés être moins épais)
			//  le calcul est plus rapide que pour les autres types de points multiples
			if (this.isNode()) {

				double epaisseurStandard_this_currentPath = 0;
				double epaisseurDissymetrie_currentPath = 0;

				//** Epaisseur standard ************************************
				epaisseurStandard_this_currentPath = this.diametre_reel_m/2 + path.epaisseurTrait_reel_m/2 + Math.max(this.espaceAutour_reel_m, path.espaceAutour_reel_m);
				//** Si 'path' a une dissymétrie du mauvais coté ***********
				if (path.hasDissymetrie() && vectorDirection_path.getSideForPoint(this) == path.getContrainteGeom().sideDissymetrie)
					epaisseurDissymetrie_currentPath = path.epaisseurDissymetrie_reel_m;
				
				
				// A présent, on module cette épaisseur selon l'angle du path en cours avec le vecteur perpendiculaire (ptPlusProche -> this)
				double cos_This_DirectionPath;
				if (B_USE_COS) {
					Vector_d v_This_PtPlpusProche_norm = new Vector_d(this, ptPlusProche).getVectUnitaireNormalDroite();
					cos_This_DirectionPath = Math.abs(vectorDirection_path.cosinusWith(v_This_PtPlpusProche_norm));

					// On restreint le cos entre MIN_COS et 1 (meme si perpendiculaire, on est à 0.5 minimum)
					// pour éviter trop d'écart de force si rotation du segment, ce qui entraine des divergences ou des oscillations
					cos_This_DirectionPath = cos_This_DirectionPath*(1-MIN_COS) + MIN_COS;
				} else
					cos_This_DirectionPath = 1;

				ecartMin = (epaisseurStandard_this_currentPath +	epaisseurDissymetrie_currentPath) * cos_This_DirectionPath;

			} else {
				// this = Junction autre que Node
				// Potentiellement plusieurs path passant par this : on parcourt les DirectionData pour voir
				//  lequel est le plus repoussé par le node (du fait de son orientation et de son épaisseur

				int nb = this.getNbDirectionData();
				for (int j=0 ; j<nb ; j++) {
					DirectionData dd = vDirectionData.get(j);

					double epaisseurStandard_currentDD_currentPath = 0;
					double epaisseurDissymetrie_currentPath = 0;
					double epaisseurDissymetrie_currentDD = 0;
					double ecartMinModulee_currentDD_currentPath = 0;

					//** Epaisseur standard ************************************
					epaisseurStandard_currentDD_currentPath = dd.pathDER.epaisseurTrait_reel_m/2 + path.epaisseurTrait_reel_m/2 + Math.max(dd.pathDER.espaceAutour_reel_m, path.espaceAutour_reel_m);

					//** Si 'path' a une dissymétrie du mauvais coté ***********
					if (path.hasDissymetrie() && vectorDirection_path.getSideForPoint(this) == path.getContrainteGeom().sideDissymetrie)
						epaisseurDissymetrie_currentPath = path.epaisseurDissymetrie_reel_m;

					//** Si le path de DD courant a une dissymétrie du mauvais coté
					if (this.hasDissymetrie() &&
							(dd.vectorDirection.getSideForPoint(ptPlusProche) == dd.pathDER.getContrainteGeom().sideDissymetrie  ||
							 dd.vectPrec.getSideForPoint(ptPlusProche) == dd.pathDER.getContrainteGeom().sideDissymetrie  ||
							 dd.vectSuiv.getSideForPoint(ptPlusProche) == dd.pathDER.getContrainteGeom().sideDissymetrie))
						epaisseurDissymetrie_currentDD = dd.pathDER.epaisseurDissymetrie_reel_m;

					// A présent, on module cette épaisseur selon l'angle de DD et du path en cours
					double cos_DirectionCurrentDD_DirectionPath;
					if (B_USE_COS) {
						double cos1, cos2, cos3;
						cos1 = Math.abs(vectorDirection_path.cosinusWith(dd.vectorDirection));
						cos2 = Math.abs(vectorDirection_path.cosinusWith(dd.vectPrec));
						cos3 = Math.abs(vectorDirection_path.cosinusWith(dd.vectSuiv));
						cos_DirectionCurrentDD_DirectionPath = Math.max(Math.max(cos1, cos2), cos3);
						// On restreint le cos entre MIN_COS et 1 (meme si perpendiculaire, on est à MIN_COS minimum)
						// pour éviter trop d'écart de force si rotation du segment, ce qui entraine des divergences ou des oscillations
						cos_DirectionCurrentDD_DirectionPath = cos_DirectionCurrentDD_DirectionPath*(1-MIN_COS) + MIN_COS;
					}
					else
						cos_DirectionCurrentDD_DirectionPath = 1;


					ecartMinModulee_currentDD_currentPath =
							(epaisseurStandard_currentDD_currentPath +
							epaisseurDissymetrie_currentPath +
							epaisseurDissymetrie_currentDD) * cos_DirectionCurrentDD_DirectionPath;

					ecartMin = Math.max(ecartMin, ecartMinModulee_currentDD_currentPath);
				}
			}


			//********* On en déduit si this doit être éloigné de path *********
			double ecartReel = this.distTo(ptPlusProche);
			if (ecartReel==0) {
				Logger.log_WARN("DIST ligne-ligne = 0 : " + this.getMainPathContenant().getInfoBulle() + " / " + path.getInfoBulle());
				continue; // On passe au path suivant
			}

			if (ecartReel < ecartMin) {
				// this est trop proche du path : on met une force de répulsion
				double distPenetr = ecartMin - ecartReel;
				double xUnitDeplacement = (this.x - ptPlusProche.x) / ecartReel;
				double yUnitDeplacement = (this.y - ptPlusProche.y) / ecartReel;

				//Ecarter les lignes du fait de dissymetries est moins prioritaire
				// que par l'epaisseur standard des lignes (les barbulles sont peu encombrantes)
				double attenuationSiDissymetrie = 1;//diametre_reel_m / (diametre_reel_m + epaisseurDissymetrie_reel_m);

				double xForce, yForce;
				xForce = xUnitDeplacement * distPenetr * factAttenuationRepulsion * attenuationSiDissymetrie;
				yForce = yUnitDeplacement * distPenetr * factAttenuationRepulsion * attenuationSiDissymetrie;

				addForceRepuls_from_OnePath(xForce, yForce);
			}
		}
	}
*/


/* OLD V19
	private void computeRepulsToNode (ArrayList<JunctionDER> vNodeProche, double factAttenuationRepulsion_complet, double factAttenuationRepulsion_horsZoneDense) {
		int sizeVNode = vNodeProche.size();
		for (int i=0; i<sizeVNode; i++) {
	        JunctionDER node = vNodeProche.get(i);
			if (node == this) continue;

			// Si this est un node ou une intersection, il faut calculer la repulsion, quoi qu'il arrive
			// Sinon, on regrade si l'un des path de this est connecté au node
			//   si oui, on ne fait rien

			// TODO_OK : idée, on repousse toujours les intersection des nodes
			// en fait OK si intersection dont l'un des path n'est pas connecté au node, mais sinon, pourquoi vouloir le repousser a tout prix ?
			//if (!this.isNode() && !this.isIntersect() && isOneOfMyPathConnectedToNode(node)) continue;
			// On ne repousse pas les junctions dont TOUS les path son connectés au node
			if (!this.isNode() && areAllOfMyPathConnectedToNode(node)) continue;

			double ecartMin = 0;

			if (this.isNode()) {
				ecartMin = this.diametre_reel_m/2 + node.diametre_reel_m/2 + Math.max(node.espaceAutour_reel_m, this.espaceAutour_reel_m);

			} else {
				// Potentiellement plusieurs path passant par this : on parcours les DirectionData pour voir
				//  lequel est le plus repoussé par le node (du fait de son orientation et de son épaisseur

				int nb = this.getNbDirectionData();
				for (int j=0 ; j<nb ; j++) {
					DirectionData dd = vDirectionData.get(j);

					double epaisseurStandard_currentDD_currentNode = 0;
					double epaisseurDissymetrie_currentDD = 0;
					double distMinModulee_currentDD_currentNode = 0;

					//** Epaisseur standard ************************************
					epaisseurStandard_currentDD_currentNode = dd.pathDER.epaisseurTrait_reel_m/2 + node.diametre_reel_m/2 + Math.max(dd.pathDER.espaceAutour_reel_m, node.espaceAutour_reel_m);

					//** Si le path de DD courant a une dissymétrie du mauvais coté
					if (this.hasDissymetrie() &&
							(dd.vectorDirection.getSideForPoint(node) == dd.pathDER.getContrainteGeom().sideDissymetrie  ||
							 dd.vectPrec.getSideForPoint(node) == dd.pathDER.getContrainteGeom().sideDissymetrie  ||
							 dd.vectSuiv.getSideForPoint(node) == dd.pathDER.getContrainteGeom().sideDissymetrie))
						epaisseurDissymetrie_currentDD = dd.pathDER.epaisseurDissymetrie_reel_m;

					// A présent, on module cette épaisseur selon l'angle de DD et du path en cours
					double cos_DirectionCurrentDD_Node;
					if (B_USE_COS) {
						double cos1, cos2, cos3;
						Vector_d v_This_Node_norm = new Vector_d(this, node).getVectUnitaireNormalDroite();
						cos1 = Math.abs(v_This_Node_norm.cosinusWith(dd.vectorDirection));
						cos2 = Math.abs(v_This_Node_norm.cosinusWith(dd.vectPrec));
						cos3 = Math.abs(v_This_Node_norm.cosinusWith(dd.vectSuiv));
						cos_DirectionCurrentDD_Node = Math.max(Math.max(cos1, cos2), cos3);
						// On restreint le cos entre MIN_COS et 1 (meme si perpendiculaire, on est à MIN_COS minimum)
						// pour éviter trop d'écart de force si rotation du segment, ce qui entraine des divergences ou des oscillations
						cos_DirectionCurrentDD_Node = cos_DirectionCurrentDD_Node*(1-MIN_COS) + MIN_COS;
					}
					else
						cos_DirectionCurrentDD_Node = 1;


					distMinModulee_currentDD_currentNode =
							(epaisseurStandard_currentDD_currentNode +
							epaisseurDissymetrie_currentDD) * cos_DirectionCurrentDD_Node;

					ecartMin = Math.max(ecartMin, distMinModulee_currentDD_currentNode);

				}
			}


			double dist = this.distTo(node);
	        if (dist==0)
				Logger.log_WARN("!! DIST ligne-ligne = 0 !!");

			else if (dist<ecartMin) {
	            // il n'y a de force que si cette condition est respectée
	            double distPenetr = ecartMin-dist;

	            // vecteur unitaire ptPlusProche -> pt
	            double xUnitDeplacement = (this.x - node.x) / dist;
	            double yUnitDeplacement = (this.y - node.y) / dist;

				double xForce, yForce;


				// SIOUX V19 14/11/2012

				// choix du facteur d'attenuation de repulsion
				// ** cas this = NODE : 
				//   facile : facteur = 1 (pas d'atténuation)
				// ** cas this <> NODE
				// si l'un des path de this est connecté au node, on prend le facteur par défaut le plus contraignant,
				//  ie prenant en compte la densité de la zone
				// [OLD]si aucun les path de this n'est connecté au node alors on prend un facteur reduit qui ne prend pas en compte
				//  la densité de la zone, ce qui permet à this de ne pas être bloqué si zone dense et traversé par un node
				//  qui lui peut bouger bcp car pas affecté par la densité de la zone
				// [NEW] pas d'atténuation, ca fonctionne mieux
				double factAttenuationRepulsion;
				if (this.isNode())
					factAttenuationRepulsion = 1;
				else if (isOneOfMyPathConnectedToNode(node))
					factAttenuationRepulsion = factAttenuationRepulsion_complet;
				else
					factAttenuationRepulsion  = 1;//factAttenuationRepulsion_horsZoneDense; // TODO_OK MENAGE variable non utilisée
				
				
				double attenuationSiDissymetrie = 1;//diametre_reel_m / (diametre_reel_m + epaisseurDissymetrie_reel_m);
				
	            xForce = xUnitDeplacement * distPenetr * factAttenuationRepulsion * attenuationSiDissymetrie;
	            yForce = yUnitDeplacement * distPenetr * factAttenuationRepulsion * attenuationSiDissymetrie;

	            this.addForceRepuls_from_Node(xForce, yForce);
	        }
	    }
	}
*/


	// TODO_KO PERF : a precalculer
//	private boolean isOneOfMyPathConnectedToNode (JunctionDER node) {
//		assert (!isNode());
//
//		for (int i=0; i<nbPathContenant; i++) {
//			PathDER pathDER = (PathDER) getPathContenantNum(i);
//			if (pathDER.isConnectedToNode(node))
//				return true;
//		}
//		return false;
//	}
	// TODO_KO PERF : a precalculer
//	private boolean areAllOfMyPathConnectedToNode (JunctionDER node) {
//		assert (!isNode());
//
//		for (int i=0; i<nbPathContenant; i++) {
//			PathDER pathDER = (PathDER) getPathContenantNum(i);
//			if (!pathDER.isConnectedToNode(node))
//				return false;
//		}
//		return true;
//	}

	public void computeForceEntrainement () {
		// Cette idee de ne prendre que l'entraineur max venait d'une zone (pres LAVER) ou ca aidait un peu
		//  mais ca cree trop de souci dans les autres zones fournies en intersection
		//  donc on laisse tomber
//		JunctionDER maxEntraineur = this.getMaxEntraineur();
//		if (maxEntraineur==null)
//			return;
//		double factorForceMaxEntraineur = this.getMaxFactorForceEntrainement();
//		
//		// Vecteur initial en REF
//		JunctionREF ptREF = this.getJunctionREF();
//		JunctionREF nodeREF = maxEntraineur.getJunctionREF();
//		double dxREF = ptREF.x - nodeREF.x;
//		double dyREF = ptREF.y - nodeREF.y;
//
//		// Vecteur courant DER
//		double dxDER = this.x - maxEntraineur.x;
//		double dyDER = this.y - maxEntraineur.y;
//
//		// Décallage du ptDER par rapport à son entraineur
//		double decalX = (dxREF - dxDER) * factorForceMaxEntraineur;
//		double decalY = (dyREF - dyDER) * factorForceMaxEntraineur;
//
//		this.addForceEntrainement(decalX, decalY);
			
			
		// Entrainement des nodes
		for (int i=0 ; i<nbInfluenceur_NODE ; i++) {
			JunctionDER nodeEntraineurDER = this.getInfluenceur_NODE(i);

			double factorForceEntraineur = getFacteurEntrainementByNode(nodeEntraineurDER, this.getFactorInfluencement_NODE(nodeEntraineurDER));

			// Vecteur initial en REF
			JunctionREF ptREF = this.getJunctionREF();
			JunctionREF nodeREF = nodeEntraineurDER.getJunctionREF();
			double dxREF = ptREF.x - nodeREF.x;
			double dyREF = ptREF.y - nodeREF.y;

			// Vecteur courant DER
			double dxDER = this.x - nodeEntraineurDER.x;
			double dyDER = this.y - nodeEntraineurDER.y;

			// Décallage du ptDER par rapport à son entraineur
			double decalX = (dxREF - dxDER) * factorForceEntraineur;
			double decalY = (dyREF - dyDER) * factorForceEntraineur;

			this.addForceEntrainement(decalX, decalY);
		}

		// Entrainement des points multiples (CONNECT_3 et INTERSECTION)
		// plus simple que les nodes : entrainement = influencement
		for (int i=0 ; i<nbInfluenceur_PTMULTIPLE ; i++) {
			JunctionDER ptMultipleEntraineurDER = this.getInfluenceur_PTMULTIPLE(i);
			double factorForceEntraineur = this.getFactorInfluencement_PTMULTIPLE(i);

			// Vecteur initial en REF
			JunctionREF ptREF = this.getJunctionREF();
			JunctionREF nodeREF = ptMultipleEntraineurDER.getJunctionREF();
			double dxREF = ptREF.x - nodeREF.x;
			double dyREF = ptREF.y - nodeREF.y;

			// Vecteur courant DER
			double dxDER = this.x - ptMultipleEntraineurDER.x;
			double dyDER = this.y - ptMultipleEntraineurDER.y;

			// Décallage du ptDER par rapport à son entraineur
			double decalX = (dxREF - dxDER) * factorForceEntraineur;
			double decalY = (dyREF - dyDER) * factorForceEntraineur;

			this.addForceEntrainement(decalX, decalY);
		}
	}


	public void computeAttractPositionInit () {
		JunctionREF ptREF = this.getJunctionREF();
		this.setForceAttractPosition(ptREF.x - this.x, ptREF.y - this.y);
	}



	//**************************************************************************
	//**************************************************************************
	// Influencement des points multiples sur les autres junctions
	//**************************************************************************
	//**************************************************************************
	
	// Calcule l'influencement de this sur les junctions alentours
	// Appelé 1 fois avant le traitement par Data_ls
	public void computeInfluencement() {
		switch (this.typeJunction) {
			case NODE:
				computeInfluencement_NODE();
				break;
			case INTERSECT:
			case CONNECT_3:
				computeInfluencement_PT_MUTIPLE();
				break;
		}
	}


	//**************************************************************************
	//    Cas des NODE
	//**************************************************************************

	private void computeInfluencement_NODE() {
		assert(this.isNode()) : "computeInfluencement_NODE() sur type : " + this.typeJunction;

		// On ne cherche pas plus loin qu'une certaine distance du NODE
		double rayon_final = 0.5 * nodeData.contrainteGeom.diametre_papier_mm/1000.0 * nodeData.echelleGeneralization.echelle_inv;
		double distInfluenceNode_Totale = rayon_final * nodeData.contrainteGeom.zit;
		double distInfluenceNode_Nulle = rayon_final * nodeData.contrainteGeom.zit * nodeData.contrainteGeom.zin;

		// Cas de node pour lesquels on ne souhaite pas qu'ils aient de l'influence (cf. params utilisateur)
		if (distInfluenceNode_Totale==0 || distInfluenceNode_Nulle==0)
			return;

		Params_ls params = Data_ls.data_ls.params;

		// Amélioration v15:
		// On procède en 2 passes
		// 1) on prend en compte les intersections répulsives
		// 2) si la situation est trop complexe
		// 3) on refait une passe spéciale qui "raccroche" aux node ces intersection répulsives

		// 1er cas simple :
		//  Si l'utilisateur souhaite le mode "trou noir" et que le node est de la categorie adéquate
		//   on fait 1 passe en désactivant les intersections repulsives
		if (params.bSimplifTrouNoir && this.nodeData.attributes.isOfCategory(params.categorySimplifTrouNoir)) {
			computeInfluencement_NODE_1passe (distInfluenceNode_Totale, distInfluenceNode_Nulle, false);
			return;
		}

		// Sinon on fait les 2 passes
		// 1) On parcourt les path qui partent du node et on applique les restrictions de répulsion sur les junctions
		computeInfluencement_NODE_1passe (distInfluenceNode_Totale, distInfluenceNode_Nulle, true);

		// 2) Détection des nodes ou il peut y avoir des problèmes d'explosion dues aux "intersections répulsives"
		if (params.bSimplifTrouNoir) { // cette optimisation est désactivable par l'utilisateur ...

			checkExpulsableJunctionMulti(distInfluenceNode_Nulle);
			if (nodeData.nbJunctionMultiExplulsable >= params.seuilComplexiteSimplifTrouNoir) {

				Logger.log_INFO2(" - " + this.getInfoBulle() + " : nb intersection = " + nodeData.nbIntersectionRepulsive);
				Logger.log_INFO2(" - " + this.getInfoBulle() + " : nb junction multiexpulsable = " + nodeData.nbJunctionMultiExplulsable);

				// 3) 2ème passe sans les intersections repulsives
				computeInfluencement_NODE_1passe (distInfluenceNode_Totale, distInfluenceNode_Nulle, false);
			}
		}
		
	}


	// Parcours tous les path partant de this lance une fonction qui va calculer l'influence sur chaque path
	private void computeInfluencement_NODE_1passe(double distInfluenceTotale, double distInfluenceNulle, boolean bWithRepulsiveIntersection) {
		THashSet<PathDER> setPathDejaTraites = new THashSet<PathDER>();
		for (int i=0; i<nbPathContenant; i++) {
			PathDER pathDER = (PathDER) getPathContenantNum(i);
			computeInfluencement_NODE_toPath_rec(pathDER, this, distInfluenceTotale, distInfluenceNulle, setPathDejaTraites, bWithRepulsiveIntersection);
		}
	}

	// Il faut avoir au préalable déterminer les connexions directes/indirectes des path à ce node
	//cette methode prend n'importe quelle junction et appelle la suivante avec un sens si c'est une extremité
	// ou appelle 2 fois la suivante avec les 2 sens si c'est une junction interne a son path (cas special d'une junction de depart type EXTREMITE)
	private void computeInfluencement_NODE_toPath_rec(PathDER pathDER, JunctionDER jDepart, double distInfluenceTotale, double distInfluenceNulle, THashSet<PathDER> setPathDejaTraites, boolean bWithRepulsiveIntersection) {
		assert(distInfluenceTotale < distInfluenceNulle);

		if (setPathDejaTraites.contains(pathDER))
			return; // Path déjà traité

		if (!pathDER.isConnectedToNode(this))
			//path pas declare comme connecté (directement ou indirectement) à this
			return;

		// On indique le pathDER comme déjà traité pour ne pas y revenir par la suite
		setPathDejaTraites.add(pathDER);

		JunctionDER jFrom = (JunctionDER) pathDER.from();
		JunctionDER jTo = (JunctionDER) pathDER.to();

		int indexDeb, indexFin, increment;
		if (jFrom == jDepart) {
			// cas jDepart = 1ere junction du path
			indexDeb = 0;
			indexFin = pathDER.getNbJunction();
			increment = 1;
			computeInfluencement_NODE_toPath_rec_1sens (pathDER, indexDeb, indexFin, increment, distInfluenceTotale, distInfluenceNulle, setPathDejaTraites, bWithRepulsiveIntersection);

		} else if (jTo == jDepart) {
			// cas jDepart = dernière junction du path
			indexDeb = pathDER.getNbJunction()-1;
			indexFin = -1;
			increment = -1;
			computeInfluencement_NODE_toPath_rec_1sens (pathDER, indexDeb, indexFin, increment, distInfluenceTotale, distInfluenceNulle, setPathDejaTraites, bWithRepulsiveIntersection);

		} else {
			// cas jDepart = junction interne, il faut appeler 2 fois la fonction de parcours
			indexDeb = pathDER.getIndexOfThisJunction(jDepart);
			indexFin = pathDER.getNbJunction();
			increment = 1;
			computeInfluencement_NODE_toPath_rec_1sens (pathDER, indexDeb, indexFin, increment, distInfluenceTotale, distInfluenceNulle, setPathDejaTraites, bWithRepulsiveIntersection);

			indexFin = -1;
			increment = -1;
			computeInfluencement_NODE_toPath_rec_1sens (pathDER, indexDeb, indexFin, increment, distInfluenceTotale, distInfluenceNulle, setPathDejaTraites, bWithRepulsiveIntersection);
		}
	}

	// !!!!!!! indexFin est exclu !!
	private void computeInfluencement_NODE_toPath_rec_1sens(PathDER pathDER, int indexDeb, int indexFin, int increment, double distInfluenceTotale, double distInfluenceNulle, THashSet<PathDER> setPathDejaTraites, boolean bWithRepulsiveIntersection) {

		// Parcours des junctions
		ArrayList<JunctionDER> vJDer = pathDER.getVJunctionDER();
		
		boolean bStopFor = false;
		for (int i=indexDeb; i!=indexFin ; i+=increment) {

			if (bStopFor) break;

			JunctionDER jDER = vJDer.get(i);
			double distToNode = this.jREF.distTo(jDER.jREF);
			if (distToNode >= distInfluenceNulle)
				// dés qu'on s'éloigne trop, on arrête (tant pis si le path revient vers le node par la suite ...)
				break;

			if (jDER.getFactorInfluencement_NODE(this) > 0)
				// On est déjà passé par ce point
				continue;

			// fonction décroissant de 1 vers 0 a mesure que la distance du pt au node
			//  va de 0 a distMax
			double factorInfluencement;
			if (distToNode<=distInfluenceTotale)
				factorInfluencement = 1;
			else {
				// d'abord en mode linéaire
				factorInfluencement = (distInfluenceNulle - distToNode) / (distInfluenceNulle - distInfluenceTotale);
				//infléchissement de la courbe de décroissance de l'influence (adoucit la transition vers la zone hors d'influence)
				factorInfluencement = factorInfluencement*factorInfluencement*factorInfluencement;
			}

			switch (jDER.typeJunction) {
				case NODE:
					break;

				case STD:
					jDER.addInfluenceur_NODE(this, factorInfluencement);
					break;

				case CONNECT_2:
				case CONNECT_3:
					jDER.addInfluenceur_NODE(this, factorInfluencement);
					// parcours des path connectés à jDER et appel récursif du traitement
					for (int j=0; j<jDER.nbPathContenant; j++) {
						PathDER pathDER_2 = (PathDER) jDER.getPathContenantNum(j);
						if (pathDER_2!=pathDER && !setPathDejaTraites.contains(pathDER_2))
							computeInfluencement_NODE_toPath_rec (pathDER_2, jDER, distInfluenceTotale, distInfluenceNulle, setPathDejaTraites, bWithRepulsiveIntersection);
					}
					break;

				case INTERSECT:
					// C'est pour pouvoir faire ce test qu'on a dû au préalable déterminer les connexions directes/indirectes des path à ce node
					if (areAllPathOfIntersectConnectedToThisNode(jDER, this)) {
						jDER.addInfluenceur_NODE(this, factorInfluencement);
						// parcours des path connectés à jDER et appel récursif du traitement
						//  uniquement si jDER est une extrémité du path
						for (int j=0; j<jDER.nbPathContenant; j++) {
							PathDER pathDER_2 = (PathDER) jDER.getPathContenantNum(j);
							if (jDER.isExtremite(pathDER_2) && pathDER_2!=pathDER && !setPathDejaTraites.contains(pathDER_2))
								computeInfluencement_NODE_toPath_rec (pathDER_2, jDER, distInfluenceTotale, distInfluenceNulle, setPathDejaTraites, bWithRepulsiveIntersection);
						}

					} else {

						if (bWithRepulsiveIntersection) {
							// CAS AVEC TRAITEMENT DES INTERSECTIONS REPULSIVES

							// cette intersection est bloquante, on stoppe le parcours du path
							//  à son niveau : elle est censée s'éloigner du node par répulsion et donc éloigner également toutes les junctions
							//  situées après
							bStopFor = true; // on ne peut pas faire un break du for car on est dans un switch ...


							//*** Amélioration v15 ***********************************************
							// on note l'intersection selon les dégats qu'elle va provoquer en étant expulsée du node
							// on teste d'abord si l'intersection est assez proche du node pour devoir bouger (sinon, elle n'est pas gênante)

							double rayonNode = nodeData.contrainteGeom.diametre_papier_mm/2 * nodeData.echelleGeneralization.echelle_inv / 1000.0;
							double espaceAutour = nodeData.contrainteGeom.espaceAutour_papier_mm * nodeData.echelleGeneralization.echelle_inv / 1000.0;
							double largeurPathOfIntersect = jDER.getLargestPathContenant_withDissy(); // + _withEspace ??

							double eloignementNaturelIntersect = rayonNode + espaceAutour + largeurPathOfIntersect/2;

							if (distToNode <= eloignementNaturelIntersect) {
								nodeData.nbIntersectionRepulsive++;
								//detailsIntersectionRepulsive += " * dist = " + distToNode + "  -  eloignement = " + eloignementNaturelIntersect + "\n";

								// cette largeur max va etre utilisée plus tard pour quantifier
								// le problème des junctions qui vont être expulsées
								nodeData.maxLargeurPathOfIntersectionRepulsive = Math.max(nodeData.maxLargeurPathOfIntersectionRepulsive, largeurPathOfIntersect);
							}

						} else {

							// CAS SANS TRAITEMENT DES INTERSECTIONS REPULSIVES

							// On continue comme de rien n'était ... ie. comme si l'intersection ne comportait que des path connectés à this ...
							jDER.addInfluenceur_NODE(this, factorInfluencement);
							// parcours des path connectés à jDER et appel récursif du traitement
							//  uniquement si jDER est une extrémité du path
							for (int j=0; j<jDER.nbPathContenant; j++) {
								PathDER pathDER_2 = (PathDER) jDER.getPathContenantNum(j);
								// il faut désormais declarer le path comme connecté au node !
								if (!pathDER_2.isConnectedToNode(this))
									pathDER_2.addNodeConnected(this);
								computeInfluencement_NODE_toPath_rec (pathDER_2, jDER, distInfluenceTotale, distInfluenceNulle, setPathDejaTraites, bWithRepulsiveIntersection);
							}
						}
					}
					break;

				default:
					assert(false) : "Type de junction inconnu : " + jDER.typeJunction;

			}
		}
	}

	private boolean areAllPathOfIntersectConnectedToThisNode (JunctionDER intersect, JunctionDER node) {
		for (int i=0; i<intersect.nbPathContenant; i++) {
			PathDER pathDER = (PathDER) intersect.getPathContenantNum(i);
			if (!pathDER.isConnectedToNode(node))
				return false;
		}
		return true;
	}
	private ArrayList<PathDER> getPathOfIntersectNotConnectedToThisNode (JunctionDER intersect, JunctionDER node) {
		ArrayList<PathDER> vRes = new ArrayList<PathDER>();
		for (int i=0; i<intersect.nbPathContenant; i++) {
			PathDER pathDER = (PathDER) intersect.getPathContenantNum(i);
			if (!pathDER.isConnectedToNode(node))
				vRes.add(pathDER);
		}
		return vRes;
	}

	private void checkExpulsableJunctionMulti(double distInfluence) {

		// Qques precalculs
		double rayonNode = nodeData.contrainteGeom.diametre_papier_mm/2 * nodeData.echelleGeneralization.echelle_inv / 1000.0;
		double espaceAutour = nodeData.contrainteGeom.espaceAutour_papier_mm * nodeData.echelleGeneralization.echelle_inv / 1000.0;
		double eloignementMiniDesJunctions = rayonNode + espaceAutour + nodeData.maxLargeurPathOfIntersectionRepulsive/2;

		// D'abord on récupère toutes les junctions dans l'emprise du node
		// On ne considère que les points multiples, car ce sont eux qui mesurent la complexité du réseau qui va
		//  être expulsé (et on s'affranchit du paramétre de simplification/redecoupage des path, qui fait varier
		//  le nombre de junction qui seraient expulsées
		ArrayList<JunctionDER> vJunctionInfluencee = new ArrayList<JunctionDER>();
		Rect_d r = new Rect_d(this, distInfluence*2);
		ArrayList vJunctionPotentiellementInfluencee = Data_ls.data_ls.getData(DataType.JUNCTION_DER_LS, r);

		for (Object o : vJunctionPotentiellementInfluencee) {
			JunctionDER j = (JunctionDER)o;
			if (j.distTo(this) < distInfluence && (j.isConnect3() || j.isIntersect()))
				vJunctionInfluencee.add(j);
		}

		// Puis pour chaque junction multiple sous l'influence de this ...
		for (JunctionDER j : vJunctionInfluencee) {

			// ... si elle fait partie d'un path connecté à this ...
			if (j.mainPathContenant != null && ((PathDER)j.mainPathContenant).isConnectedToNode(this) &&
				// ... et qu'elle aurait dû etre sous influence du node
				// le seul cas ou ca n'aurait pas été le cas est une intersection dont
				// l'un des path n'est pas connecté à this
				(!j.isIntersect() || (j.isIntersect() && areAllPathOfIntersectConnectedToThisNode(j, this))) &&
				// ... et qu'elle n'est pas déclarée comme influencée par this (à cause d'une intersection expulsive) ...
				j.getFactorInfluencement_NODE(this)==0.0 &&
				// ... et qu'elle est suffisament proche du node pour être déplacée
				j.distTo(this) <= eloignementMiniDesJunctions) {

				nodeData.nbJunctionMultiExplulsable ++;
				//Logger.log_ERROR("  - j : dist = " + j.distTo(this) + "  -  eloignement mini = " + eloignementMiniDesJunctions);
				
			}
		}
	}




	//**************************************************************************
	//    Cas des points multiples (CONNECT_3, INTERSECT)
	//**************************************************************************

	// Autour d'un point multiple, le traitement est légèrement plus simple et s'effectue
	//  en 1 étape seulement (pas de précalcul de connexité indirecte nécessaire)
	private void computeInfluencement_PT_MUTIPLE() {
		assert(this.isConnect3() || this.isIntersect()) : "computeRestrictionRepulsionEtForceEntrainement_autour_PT_MUTIPLE() sur type : " + this.typeJunction;

		// Set pour éviter de repasser plusieurs fois sur le meme path
		// (cf. appel récursif + cas des path qui reviennent sur les mêmes extrémités)
		HashSet<PathDER> setPathDejaTraites = new HashSet<PathDER>();

		for (int i=0; i<nbPathContenant; i++) {
			PathDER pathDER = (PathDER) getPathContenantNum(i);

			// On ne sait pas de quel cote sera la dissy ... donc le calcul suivant est trop optimiste
			//  d'un cote de la dissy et trop pessimiste de l'autres cote ...
			double rayonFinal = this.getLargestPathContenant_withDissy()/2;
			double distInfluenceIntersect = rayonFinal * Data_ls.data_ls.params.influenceIntersection;
			assert(distInfluenceIntersect > 0); // obligé car paramétrage impose influence >0

			// Suivant que this est extrémité ou non (cas d'une INTERSECTION) l'appel
			//  pour le parcours des junction du path n'est pas le même
			int nbJunctionOfThePath = pathDER.getVJunctionDER().size();
			int index = pathDER.getIndexOfThisJunction(this);
			if (index == 0)
				computeInfluencement_PT_MULTIPLE_toPath(pathDER, 1, nbJunctionOfThePath, 1, distInfluenceIntersect, setPathDejaTraites);
			else if (index == nbJunctionOfThePath-1)
				computeInfluencement_PT_MULTIPLE_toPath(pathDER, nbJunctionOfThePath-2, -1, -1, distInfluenceIntersect, setPathDejaTraites);
			else if (index > 0 && index < (nbJunctionOfThePath-1)) {
				// Cas d'une intersection placée au milieu d'un path, il faut
				//  parcourir le path dans les 2 sens autour de cette intersection
				computeInfluencement_PT_MULTIPLE_toPath(pathDER, index+1, nbJunctionOfThePath, 1, distInfluenceIntersect, setPathDejaTraites);
				setPathDejaTraites.remove(pathDER); // utile si on veut que l'appel suivant fasse un traitement ...
				computeInfluencement_PT_MULTIPLE_toPath(pathDER, index-1, -1, -1, distInfluenceIntersect, setPathDejaTraites);
			} else {
				assert(false):"Junction non trouvée dans l'un de ses path, junction = " + this + " , index = " + index;
			}
		}
	}


	private void computeInfluencement_PT_MULTIPLE_toPath(PathDER pathDER, int indexJ1, int indexJ2, int incrParcours, double distMax, HashSet<PathDER> setPathDejaTraites) {
		assert (incrParcours==1 && indexJ2>=indexJ1)||(incrParcours==-1 && indexJ1>=indexJ2) : "Mauvais arguments";


		if (setPathDejaTraites.contains(pathDER))
			return; // Path déjà traité

		// On indique le pathDER comme déjà traités pour ne pas y revenir par la suite
		setPathDejaTraites.add(pathDER);

		// Parcourt des junctions
		boolean bStopFor = false;
		for (int i=indexJ1; i!=indexJ2 ; i+=incrParcours) {

			if (bStopFor) break;

			JunctionDER jDER = pathDER.getVJunctionDER().get(i);
			double distToNode = this.jREF.distTo(jDER.jREF);
			if (distToNode > distMax)
				// dés qu'on s'éloigne trop, on arrête (tant pis si le path revient vers le node par la suite ...)
				break;

			if (jDER.getFactorInfluencement_PTMULTIPLE(this) > 0)
				// On est déjà passé par ce point
				continue;

			double factorInfluencement = 1 - (distToNode / distMax);
			// adoucit la transition vers la zone hors d'influence
			factorInfluencement = factorInfluencement * factorInfluencement;


			switch (jDER.typeJunction) {
				case NODE:
				case INTERSECT:
				case CONNECT_3:
					bStopFor = true; // on ne peut pas faire un break du for car on est dans un switch ...
					break;

				case STD:
					jDER.addInfluenceur_PTMULTIPLE(this, factorInfluencement);
					break;

				case CONNECT_2:
					jDER.addInfluenceur_PTMULTIPLE(this, factorInfluencement);
					// parcours des path connectés à jDER et appel récursif du traitement
					// a priori, il n'y a qu'un path connecté à examiner ...
					for (int j=0; j<jDER.nbPathContenant; j++) {
						PathDER pathDER_2 = (PathDER) jDER.getPathContenantNum(j);
						if (pathDER_2==pathDER) continue;
						// Suivant que la junction CONNECT_2 est la 1ere ou la derniere
						//  extrémité du path, l'appel récursif n'est pas le même
						if (jDER==pathDER_2.getFirstJunction())
							computeInfluencement_PT_MULTIPLE_toPath(pathDER_2, 0, pathDER_2.getVJunctionDER().size(), 1, distMax, setPathDejaTraites);
						else if (jDER==pathDER_2.getLastJunction())
							computeInfluencement_PT_MULTIPLE_toPath(pathDER_2, pathDER_2.getVJunctionDER().size()-1, -1, -1, distMax, setPathDejaTraites);
						else
							assert(false):"Junction CONNECT_2 pas extrémité d'un de ses path";
					}
					break;

				default:
					assert(false) : "Type de junction inconnu : " + jDER.typeJunction;
			}
		}
	}


	


	//-------------------  Gestion des influenceurs -------------------------
	// chaque junction possède un tableau avec ses influenceurs de type NODE et 
	//  un autres avec ses influenceurs de type ptMultiple
	private int NB_MAX_INFLUENCEUR = 10; // TODO ALGO : rendre paramétrable

	//-------------------  cas des NODE -----------------------
	private int nbInfluenceur_NODE;
	private JunctionDER[] tab_Influenceur_NODE;
	private double[] tab_factorInfluencement_NODE;
	public void reinitInfluenceur_NODE() {nbInfluenceur_NODE = 0; /*maxFactorForceEntrainement = -1; maxEntraineur = null;*/}
	public int getNbInfluenceur_NODE() {return nbInfluenceur_NODE;}
	public JunctionDER getInfluenceur_NODE(int i) {return tab_Influenceur_NODE[i];}
	public double getFactorInfluencement_NODE(int i) {return tab_factorInfluencement_NODE[i];}

	private void addInfluenceur_NODE(JunctionDER node, double factor) {
		assert (node.typeJunction == TypeJunction.NODE) : "addInfluenceur_NODE() pas avec un node";
		assert (factor>0 && factor<=1) : "Facteur d'influencement pas compris dans ]0;1]";
		assert (nbInfluenceur_NODE<NB_MAX_INFLUENCEUR): "trop de nodes influenceur";
		assert (getFactorInfluencement_NODE(node) == 0.0):"influenceur deja ajoute pour cette junction";

		// Pour optimiser la mémoire, on n'initilise ca que si nécessaire
		// (toutes les junctions n'en ont pas besoin)
		if (tab_Influenceur_NODE == null) {
			tab_Influenceur_NODE = new JunctionDER[NB_MAX_INFLUENCEUR];
			tab_factorInfluencement_NODE = new double[NB_MAX_INFLUENCEUR];
		}

		tab_Influenceur_NODE[nbInfluenceur_NODE] = node;
		tab_factorInfluencement_NODE[nbInfluenceur_NODE] = factor;
		nbInfluenceur_NODE++;
	}
	// renvoi 0 si le node n'est pas trouvé parmi les entraineurs
	public double getFactorInfluencement_NODE (JunctionDER node) {
		assert (node.typeJunction == TypeJunction.NODE) : "getFactorInfluencement_NODE() pas sur un node";
		for (int i=0; i<nbInfluenceur_NODE; i++) {
			if (tab_Influenceur_NODE[i] == node)
				return tab_factorInfluencement_NODE[i];
		}
		return 0.0;
	}

	//-------  cas des ptMultiple (CONNECT_3 et INTERSECT) --------
	private int nbInfluenceur_PTMULTIPLE;
	private JunctionDER[] tab_Influenceur_PTMULTIPLE;
	private double[] tab_factorInfluencement_PTMULTIPLE;
	public void reinitInfluenceur_PTMULTIPLE() {nbInfluenceur_PTMULTIPLE = 0;}
	public int getNbInfluenceur_PTMULTIPLE() {return nbInfluenceur_PTMULTIPLE;}
	public JunctionDER getInfluenceur_PTMULTIPLE(int i) {return tab_Influenceur_PTMULTIPLE[i];}
	public double getFactorInfluencement_PTMULTIPLE(int i) {return tab_factorInfluencement_PTMULTIPLE[i];}

	private void addInfluenceur_PTMULTIPLE(JunctionDER ptMultiple, double factor) {
		assert (ptMultiple.typeJunction == TypeJunction.CONNECT_3 || ptMultiple.typeJunction == TypeJunction.INTERSECT) : "addEntraineur() pas avec un ptMultiple";
		assert (factor>0 && factor<=1) : "Facteur d'influencement pas compris dans ]0;1]";
		assert (nbInfluenceur_PTMULTIPLE<NB_MAX_INFLUENCEUR): "trop de ptMultiple influenceur";
		assert (getFactorInfluencement_PTMULTIPLE(ptMultiple) == 0.0):"influenceur deja ajoute pour cette junction";

		// Pour optimiser la mémoire, on n'initilise ca que si nécessaire
		// (toutes les junctions n'en ont pas besoin)
		if (tab_Influenceur_PTMULTIPLE==null) {
			tab_Influenceur_PTMULTIPLE = new JunctionDER[NB_MAX_INFLUENCEUR];
			tab_factorInfluencement_PTMULTIPLE = new double[NB_MAX_INFLUENCEUR];
		}

		tab_Influenceur_PTMULTIPLE[nbInfluenceur_PTMULTIPLE] = ptMultiple;
		tab_factorInfluencement_PTMULTIPLE[nbInfluenceur_PTMULTIPLE] = factor;
		nbInfluenceur_PTMULTIPLE++;
	}
	// renvoi 0 si le ptMultiple n'est pas trouvé parmi les influenceurs
	public double getFactorInfluencement_PTMULTIPLE (JunctionDER ptMultiple) {
		assert (ptMultiple.typeJunction == TypeJunction.CONNECT_3 || ptMultiple.typeJunction == TypeJunction.INTERSECT) : "getFactorEntrainement() pas sur un ptMultiple";
		for (int i=0; i<nbInfluenceur_PTMULTIPLE; i++) {
			if (tab_Influenceur_PTMULTIPLE[i] == ptMultiple)
				return tab_factorInfluencement_PTMULTIPLE[i];
		}
		return 0.0;
	}

	
	
	//**************************************************************************
	//       GESTION DES FORCES
	//**************************************************************************

	// Les forces seg->node et node->node sont stockée dans le le NodeData pour économiser de la mémoire
//	private ForceAccumulator accuForceRepuls_Seg_Node;
//	private ForceAccumulator accuForceRepuls_Node_Node;
	// Les forces seg->seg et node->seg sont stockée dans les segments, et sont récupérée au moment de calculer la resultante finale

	
	private Force forceAttractPosition;
	
//	public Force resultForceRepuls_from_AllPath;
//	public Force resultForceRepuls_from_AllNode;
	
	public Force resultForceEntrainement_by_AllNode;

	// pour le respect de la forme, on utilise un accumulateur, pour faire une moyenne
	//  sinon, pour les pts multiples, la resultante serait trop forte
//	public ForceAccumulator accuForceRespectForme;


	private void initForces() {
		if (isNode()) {
			nodeData.accuForceRepuls_Seg_Node = new ForceAccumulator(TypeForce.MAX);
			nodeData.accuForceRepuls_Node_Node = new ForceAccumulator(TypeForce.SUM);
		}
		
//		resultForceRepuls_from_AllPath = new Force();
//	    resultForceRepuls_from_AllNode = new Force();
		
		resultForceEntrainement_by_AllNode = new Force();
		forceAttractPosition = new Force();
//		accuForceRespectForme = new ForceAccumulator(TypeForce.MEAN);
	}

	public void resetForces() {
		if (isNode()) {
			nodeData.accuForceRepuls_Seg_Node.clear();
			nodeData.accuForceRepuls_Node_Node.clear();
		}
		
//		resultForceRepuls_from_AllPath.clear();	    
//		resultForceRepuls_from_AllNode.clear();
		
		resultForceEntrainement_by_AllNode.clear();
		forceAttractPosition.clear();
//		accuForceRespectForme.clear();
	}

	private boolean checkForce(double dx, double dy) {
		assert !(Double.isNaN(dx) || Double.isNaN(dy)) : "Force NaN";
		assert !(Double.isInfinite(dx) || Double.isInfinite(dy)) : "Force Infinie";
		return true;
	}


//	private void addForceRepuls_from_OnePath (double dx, double dy) {
//		resultForceRepuls_from_AllPath.add(dx, dy);
//	}

//	public void addForceRepuls_from_Node (double dx, double dy) {
//		assert checkForce (dx, dy);
//		accuForceRepuls_from_Node.add(dx, dy);
//	}
	public void addForceEntrainement(double dx, double dy) {
		assert checkForce (dx, dy);
		resultForceEntrainement_by_AllNode.add (dx, dy);
	}

	public void setForceAttractPosition(double dx, double dy) {
		forceAttractPosition.setForce(dx, dy);
	}

	// synchronized, cf.  SegmentDER.computeForceRespectForme()
//	synchronized public void accuForceRespectForme(double dx, double dy) {
//		assert checkForce (dx, dy);
//		accuForceRespectForme.add(dx, dy);
//	}

	public Force getResultForceRepuls_Seg_Node() {
		return nodeData.accuForceRepuls_Seg_Node.getForce();
	}
	public Force getResultForceRepuls_Node_Node() {
		return nodeData.accuForceRepuls_Node_Node.getForce();
	}
	
	// Cette force est issue des segments qui ont pour extrémité this
	public Force getResultForceRepuls_Seg_Seg() {
		//return resultForceRepuls_from_AllPath;
		
		// Cas particulier d'un node isolé
		if (nbSeg==0)
			return new Force(0,0);
	
		// On fait la moyenne des forces affectées à this pour chaque segment connectés à this
		ForceAccumulator resultForce = new ForceAccumulator(TypeForce.MEAN);

		if (nbSeg>=1)
			resultForce.add(seg1.getForceRepulsSegForThisJunction(this));
		if (nbSeg>=2)
			resultForce.add(seg2.getForceRepulsSegForThisJunction(this));
		if (nbSeg>=3) {
			// le reste des segments connectés à this est dans un vecteur
			for (int i = 0; i<nbSeg-2; i++) {
				SegmentDER seg = vSeg_autre.get(i);
				resultForce.add(seg.getForceRepulsSegForThisJunction(this));
			}
		}
		
		return resultForce.getForce();
	}
	
	// Cette force est issue des segments qui ont pour extrémité this
	public Force getResultForceRepuls_Node_Seg() {
		// Cas particulier d'un node isolé
		if (nbSeg==0)
			return new Force(0,0);
		
		// On fait la moyenne des forces affectées à this pour chaque segment connectés à this
		ForceAccumulator resultForce = new ForceAccumulator(TypeForce.MEAN);

		if (nbSeg>=1)
			resultForce.add(seg1.getForceRepulsNodeForThisJunction(this));
		if (nbSeg>=2)
			resultForce.add(seg2.getForceRepulsNodeForThisJunction(this));
		if (nbSeg>=3) {
			// le reste des segments connecté à this est dans un vecteur
			for (int i = 0; i<nbSeg-2; i++) {
				SegmentDER seg = vSeg_autre.get(i);
				resultForce.add(seg.getForceRepulsNodeForThisJunction(this));
			}
		}
		
		return resultForce.getForce();
	}
	
	
	public Force getForceAttractPosition () {
		return forceAttractPosition;
	}
	
	public Force getResultForceEntrainement_by_AllNode() {
		return resultForceEntrainement_by_AllNode;
	}
	
	// Cette force est issue des segments qui ont pour extrémité this
	public Force getResultForceRespectForme() {
		// Cas particulier d'un node isolé
		if (nbSeg==0)
			return new Force(0,0);
		
		// On fait la moyenne des forces affectées à this pour chaque segment connectés à this
		ForceAccumulator resultForce = new ForceAccumulator(TypeForce.MEAN);

		if (nbSeg>=1)
			resultForce.add(seg1.getForceRespectFormeForThisJunction(this));
		if (nbSeg>=2)
			resultForce.add(seg2.getForceRespectFormeForThisJunction(this));
		if (nbSeg>=3) {
			// le reste des segments connecté à this est dans un vecteur
			for (int i = 0; i<nbSeg-2; i++) {
				SegmentDER seg = vSeg_autre.get(i);
				resultForce.add(seg.getForceRespectFormeForThisJunction(this));
			}
		}
		
		return resultForce.getForce();
	}


	// Ne prend pas en compte la force de frottement
	private Force forceResult = new Force(0,0);
	private void computeForceResult(Params_ls params) {
		// Calcul de la force résultante
		//Force forceResult = new Force(0,0);
		forceResult.clear();

		double factAttenuationRepulsion = 1;//getFactAttenuationRepulsion_complet();
		
		Force meanForce;
		meanForce = getResultForceRepuls_Seg_Seg();
		forceResult.dx += meanForce.dx * params.poidsForce_Repuls_from_PATH * factAttenuationRepulsion;
		forceResult.dy += meanForce.dy * params.poidsForce_Repuls_from_PATH * factAttenuationRepulsion;

	    meanForce = getResultForceRepuls_Node_Seg();
	    forceResult.dx += meanForce.dx * params.poidsForce_Repuls_from_NODE;
	    forceResult.dy += meanForce.dy * params.poidsForce_Repuls_from_NODE;
		
		if (isNode()) {
			meanForce = getResultForceRepuls_Seg_Node();
			forceResult.dx += meanForce.dx * params.poidsForce_Repuls_from_PATH;
			forceResult.dy += meanForce.dy * params.poidsForce_Repuls_from_PATH;

			meanForce = getResultForceRepuls_Node_Node();
			forceResult.dx += meanForce.dx * params.poidsForce_Repuls_from_NODE;
			forceResult.dy += meanForce.dy * params.poidsForce_Repuls_from_NODE;
		}


	    meanForce = getResultForceEntrainement_by_AllNode();
	    forceResult.dx += meanForce.dx * params.poidsForce_Entrainement_by_NODE;
	    forceResult.dy += meanForce.dy * params.poidsForce_Entrainement_by_NODE;

	    meanForce = getResultForceRespectForme();
	    forceResult.dx += meanForce.dx * params.poidsForce_RespectForme_PATH;
	    forceResult.dy += meanForce.dy * params.poidsForce_RespectForme_PATH;

		// ******* Force d'attraction *******************************************
		// il n'y a qu'un vecteur de force d'attraction de position, quel que soit le type de point
		// il faut donc savoir quel est le type de point pour appliquer le bon poids
	    double poidsAttract=0;
	    if (isNode())
			poidsAttract = params.poidsForce_AttractPosition_NODE;
		else
			poidsAttract = params.poidsForce_AttractPosition_PATH;

		meanForce = forceAttractPosition;
		forceResult.dx += meanForce.dx * poidsAttract;
		forceResult.dy += meanForce.dy * poidsAttract;

	}



	//**************************************************************************
	//       DYNAMIQUE
	//**************************************************************************

	public double dxVitesse, dyVitesse;

	public void bouge(Params_ls params) {
		//double deltaT = params.pasTemps; // avec > 0.8 ca diverge

		computeForceResult(params);

		// on retranche le frottement du a la vitesse
		forceResult.dx -= dxVitesse*params.facteurFrottement;
		forceResult.dy -= dyVitesse*params.facteurFrottement;

		// Nouvelle vitesse
		dxVitesse += params.pasTemps * forceResult.dx;
		dyVitesse += params.pasTemps * forceResult.dy;

		// Enfin, MAJ de la position du point
		x += params.pasTemps * dxVitesse;
		y += params.pasTemps * dyVitesse;
	}


	public void snapToZone(Params_ls params, double old_x, double old_y) {
		Zone zone = jREF.zoneIfIntersectSegZone;
		if (zone == null) return;

		if (zone.typeZone == Zone.TypeZone.RECT) {
			UtilsGeom.movePointToClosestBord_rect(zone, this);
		} else if (zone.typeZone == Zone.TypeZone.ROND) {
			UtilsGeom.movePointToClosestBord_ellipse(zone, this);
		} else
			assert (false);

		// On met à jour la vitesse car on a forcé la position
		// (sinon, ca peut donner des vitesse bizarres dans certains cas, au fil des iterations)
		dxVitesse = (x - old_x) / params.pasTemps;
		dyVitesse = (y - old_y) / params.pasTemps;

	}


	
	@Override
	String getInfoDetaille_specific() {
		String res = "";

		res += "Nb node influenceur        : " + getNbInfluenceur_NODE() + "\n";
		for (int i=0; i<getNbInfluenceur_NODE() ; i++) {
			JunctionDER node = getInfluenceur_NODE(i);
			res += " - " + getInfluenceur_NODE(i) + " : " + "\n";
			res += "  * influencement        = " + UtilsCalcul.round2(getFactorInfluencement_NODE(i)) + "\n";
			res += "  * attenuat° mvt def    = " + UtilsCalcul.round2((1-getFactorInfluencement_NODE(i))) + "\n";
			res += "   complexité node       = " + UtilsCalcul.round2(getInfluenceur_NODE(i).nodeData.complexite_apresSimplif) + "\n";
			res += "   reduction influence   = " + UtilsCalcul.round2(getInfluenceur_NODE(i).nodeData.factReductionInfluenceNode) + "\n";
			res += "  * attenuat° mvt adapt  = " + UtilsCalcul.round2(getAttenuationRepulsionForNode (node,getFactorInfluencement_NODE(i))) + "\n";
			res += "  * entrain adapt nomove = " + UtilsCalcul.round2(getFactorInfluencement_NODE(i)*node.nodeData.factReductionInfluenceNode) + "\n";
			res += "  * entrain adapt move   = " + UtilsCalcul.round2(getFacteurEntrainementByNode(node, getFactorInfluencement_NODE(i))) + "\n";
		}
		res += "Nb pt multiple influenceur : " + getNbInfluenceur_PTMULTIPLE() + "\n";
		for (int i=0; i<getNbInfluenceur_PTMULTIPLE() ; i++) {
			res += " - " + getInfluenceur_PTMULTIPLE(i) + " : " + "\n";
			res += "   -> influencement   = " + getFactorInfluencement_PTMULTIPLE(i) + "\n";
			res += "   -> attenuation mvt = " + (1-getFactorInfluencement_PTMULTIPLE(i)) + "\n";
		}
		res += "\nComplexité de la zone = " + UtilsCalcul.round2(jREF.complexiteZone) + "\n";
		res += "Atténuation de mouvement = " + UtilsCalcul.round2(getAttenuation_zoneDense()) + "\n";
		res += "\n=> Attenuation GLOBALE  = " + UtilsCalcul.round2(getFactAttenuationRepulsion_complet()) + "\n";


		res += "\nVitesse relative : " + Math.sqrt(dxVitesse*dxVitesse + dyVitesse*dyVitesse) / diametre_reel_m + "\n";


		return res;
	}
	
}
